package com.jw.debezuim.config.executor;

import com.jw.debezuim.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author jw
 * @version 1.0
 * @description 异步执行定时器
 * @date 2019/11/19 16:55
 */
@Component
@Slf4j
public class AsyncScheduler implements AsyncSchedulerMBean {

    /**
     * 定时器线程池
     */
    private ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
        private final AtomicInteger mThreadNum = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncScheduler(定时器)-thread-" + mThreadNum.getAndIncrement());
        }
    });

    /**
     * 执行器线程池
     */
    @Autowired
    private Executor executor;

    @PostConstruct
    public void init() {
        this.scheduledExecutor.setRemoveOnCancelPolicy(true);
        ObjectName mbeanName = null;
        try {
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            mbeanName = new ObjectName(Constants.BASE_PACKAGE_NAME + ":type=AsyncScheduler");
            server.registerMBean(this, mbeanName);
        } catch (Exception e) {
            log.error("getInstance registerMBean catch", e);
        }
    }

    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
        return scheduledExecutor.schedule(this.wrapAsyncCommand(command), delay, unit);
    }

    public ScheduledFuture<?> schedule(Runnable command, Date executTime) {
        return scheduledExecutor.schedule(this.wrapAsyncCommand(command), executTime.getTime()-System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    public ScheduledFuture<?> scheduleWithFixedHour(Runnable command, int fixedHour) {
        long delay;
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, fixedHour);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        delay = (calendar.getTimeInMillis() - System.currentTimeMillis()) / 1000;
        if (delay < 0) {
            delay += 86400;//86400 = 24 * 60 * 60 一天
        }
        return scheduledExecutor.schedule(this.wrapAsyncCommand(command), delay, TimeUnit.SECONDS);
    }

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        return scheduledExecutor.scheduleAtFixedRate(this.wrapAsyncCommand(command), initialDelay, period, unit);

    }

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit, int execCount) {
        WrapAsyncCommand runnable = this.wrapAsyncCommand(command, execCount);
        ScheduledFuture<?> scheduledFuture = scheduledExecutor.scheduleAtFixedRate(runnable, initialDelay, period, unit);
        runnable.setScheduledFuture(scheduledFuture);
        return scheduledFuture;

    }

    private Runnable wrapAsyncCommand(Runnable command) {
        return new WrapAsyncCommand(command);
    }

    private WrapAsyncCommand wrapAsyncCommand(Runnable command, int execCount) {
        return new WrapAsyncCommand(command, execCount);
    }

    @Override
    public int taskTotalSize() {
        return scheduledExecutor.getQueue().size();
    }

    private class WrapAsyncCommand implements Runnable {

        private Runnable source;

        private boolean forever = true;

        private volatile int execCount;

        private ScheduledFuture scheduledFuture;

        public WrapAsyncCommand(Runnable source) {
            this.source = source;
        }

        public WrapAsyncCommand(Runnable source, int execCount) {
            this(source);
            this.execCount = execCount;
            this.forever = false;
        }

        public void setScheduledFuture(ScheduledFuture scheduledFuture) {
            this.scheduledFuture = scheduledFuture;
        }

        @Override
        public void run() {
            if (!forever) {
                if (execCount-- <= 0) {
                    scheduledFuture.cancel(false);
                    scheduledFuture = null;//help gc
                    return;
                }
            }
            //将定时任务交给任务执行线程池异步处理
            AsyncScheduler.this.executor.execute(source);
            log.debug("异步处理定时任务");
        }
    }

    /**
     * 使用演示
     * @throws InterruptedException
     */
    public void demo() throws InterruptedException {
        //demo: 指定时间(触发时间后的30秒)执行一次
        this.schedule(() -> {
            log.info("hello 指定[{}]时执行一次", new Date().toLocaleString());
        }, new Date(System.currentTimeMillis() + 30000));
        //demo: 仅三秒后执行一次
        this.schedule(new Runnable() {
            public void run() {
                log.info("hello 仅三秒后执行一次: " + new Date().toLocaleString());
            }
        }, 3L, TimeUnit.SECONDS);

        //demo: 每天晚上23点执行一次
        this.scheduleWithFixedHour(new Runnable() {
            public void run() {
                log.info("hello 每天晚上23点执行一次: " + new Date().toLocaleString());
            }
        },23);

        //demo: 一秒后开始执行,之后每隔三秒执行一次
        this.scheduleAtFixedRate(new Runnable() {
            public void run() {
                log.info("hello 一秒后开始执行,之后每隔三秒执行一次: " + new Date().toLocaleString());
            }
        }, 1L, 3L, TimeUnit.SECONDS);

        //demo: 立即开始执行,之后每隔2秒执行一次,总共执行三次
        this.scheduleAtFixedRate(new Runnable() {
            public void run() {
                log.info("hello 立即开始执行,之后每隔2秒执行一次,总共执行三次: " + new Date().toLocaleString());
            }
        }, 0L, 2L, TimeUnit.SECONDS, 3);//只执行3次
    }


}